<?php 

class Validation {
	
	/**
	 * przekazywana w konstruktorze instancja klasy modelu [pref. rozszerzajaca MyModel]
	 */
	public $table;
	
	/**
	 * Miejsce na przechowywanie instancji edytowanego wpisu (w postaci hasza)
	 */
	public $_current = null;
	public $_currentRow = null;

	public function current(){
		return Utils::a2o($this->_current);
	}
	public function row(){
		
		function cast(Zend_Db_Table_Row $row){
			return $row;
		}
		
		return $this->_currentRow ? cast($this->_currentRow) : null;
	}
	
	/**
	 * convienience dostęp do requestu
	 */
	public $rq;

	/**
	 * Przygotowanie validacji 
	 * Enum_Operation::KLUCZ => nazwa metody w klasie 
	 */
	public $_prepare = array();
	
	/**
	 * Testy validacji 
	 * Enum_Operation::KLUCZ => array metod w klasie 
	 */
	public $_tests = array();
	
	/**
	 * Zakończenie validacji (intencja by wywołać po [udanym] zapisie) 
	 * Enum_Operation::KLUCZ => nazwa metody w klasie 
	 * jesliokrelone to: po walidacji zwracany jest obiekt z dwoma polami: 
	 * validator (this), oraz methodName -> nazwa metody kończącej 
	 */
	public $_finish = array();
	
	/**
	 * Geter instancji modelu dla podpowiadania składni
	 */
	
	private function caster(MyModel $table){
		return $table;
	}
	
	public function getTable(){
		return $this->caster($this->table);
	}
	
	/**
	 * Konstruktor
	 */
	public function __construct(Zend_Db_Table_Abstract $table){
		$this->table = $table;
		$this->rq = Zend_Controller_Front::getInstance()->getRequest();
	}
	
	public function setCurrent(){
		
		$prime = Utils::arrayFetch($this->table->info('primary'));
		
		// TODO da sie prosciej -. load i delete mozna naraz (bo nigdy niema id w innych requestach)
		// -> a jak nawet jest to tzreba inaczej to wyczaić w danym miejscu i tyle
		if($this->rq->getParam('my_action_type')=='load'){	// load
			$id = $this->rq->getParam('id'); 
		} else if(is_numeric($this->rq->getParam($prime))){	// save (insert)
			$id = $this->rq->getParam($prime);
		} else if($this->rq->getParam('id')){ 				// delete
			$id = $this->rq->getParam('id');
		} else if ($this->rq->getParam('w')){				// edit
			$id =  Utils::getValueFromWhere(
				$prime,$this->rq->getParam('w')
			);	
		} else $id = null;
		
		if ($id && is_numeric($id)){
			$row = $this->table->find($id)->current();
			$this->_currentRow = $row;
			$this->_current = $row->toArray();	
		} else {
			$this->_currentRow = null;
			$this->_current = null;// insert
		}
		
		return $this->_current;
	}
	
	/**
	 * Statyczne metody do komunikacji zwrotnej z interfejsem 
	 */
	// kończy działanie aplikacji i zwraca komunikat błędu
	static public function react($msg){
		if($msg !== true) die(json_encode(array(
			"success"=>false,
			"msg"=>$msg,
			"rows"=>array(),
			"data"=>null
		)));
	}
	// kończy prace i zwraca komunikat sukcesu
	static public function finish($msg=null,$data=null,$rows=array(),$info=array()){
		$response = array("success"=>true,"data"=>$data,"rows"=>$rows,"msg"=>$msg?$msg:'Operacja wykonana pomyslnie.');
		die(json_encode(array_merge($response,$info)));
	}
	
	// to co powyżej ale nie zabija apliakcji (np w celu obsłużenia logowania)
	static public function sayOk($msg=null,$data=null,$rows=array(),$info=array()){
		$response = array("success"=>true,"data"=>$data,"rows"=>$rows,"msg"=>$msg?$msg:'Operacja wykonana pomyslnie.');
		echo (json_encode(array_merge($response,$info)));
	}
	
	// to co powyżej ale nie zabija apliakcji (np w celu obsłużenia logowania)
	static public function sayNo($msg=null,$data=null,$rows=array(),$info=array()){
		$response = array("success"=>false,"data"=>$data,"rows"=>$rows,"msg"=>$msg?$msg:'Operacja nie wykonana pomyslnie.');
		echo (json_encode(array_merge($response,$info)));
	}
	
	/**
	 * 
	 * Gotowe metody do wykorzystania przy walidacji
	 *   
	 */
	
	// czy param jest unique
	public function paramIsUnique($param,$id_field,$message){
		$rq = Zend_Controller_Front::getInstance()->getRequest();
		
		$notMe = !is_array($this->_current) ? '' : 
			" AND $id_field != ".$this->_current[$id_field]; 
		
		if(is_array($param)){
			$where = '';
			foreach($param as $p){
				if($where!='')$where.=' AND ';
				$val = trim($rq->getParam($p));
				if($val==''){
					$where .= "($p = '' OR $p IS NULL)";
				} else {
					$where .= Utils::quote("$p LIKE ? ",$val);
				}
			}
			$where.=$notMe;
		} else {
			$where = Utils::quote("$param LIKE ?".$notMe,trim($rq->getParam($param))); 
		}
		$test = $this->table->fetchAll($where)->count()>0;
		
		return $test ? $message : true;
	}
	
	/**
	 * czy pk tabeli jest pusty w rq - znaczy nie edytujemy tylko wstawiamy 
	 */
	public function isNew(){
		return !$this->rq->getParam(Utils::arrayFetch($this->table->info('primary')));		
	}
	
	public function historyIsNewest($fix){
		$r = $this->rq;
		$table = $this->getTable();
		$fix = 'user_history_'.$fix.'_';
		$where = Utils::quote(
			$fix.'user_id = ? AND '.$fix.'date_since >= ?',
			array($r->getParam($fix.'user_id'),$r->getParam($fix.'date_since'))
		);
		$test = $table->q($where)->rowCount()>0;
		return $test ? 'Data początku obowiązywania atrybutu musi być wyższa niż poprzednia data w historii atrybutu.' :true;
	}
	
	// check if it was newest and wont be anymore - edit
	public function historyIsNotBeheaded($x){
		$fix = 'user_history_'.$x.'_';
		if($this->rq->getParam('f')==$fix.'date_since'){// obniżamy datę
			$test = strtotime($this->_current[$fix.'date_since']) 
				> strtotime($this->rq->getParam('v'));
			if($test){// był mnajnowszy?
				$where_tpl = $fix.'user_id = ? AND '.$fix.'date_since >= ? AND '.$fix.'id != ?';
				$where = Utils::quote($where_tpl,array(
					$this->_current[$fix.'user_id'],$this->_current[$fix.'date_since'],$this->_current[$fix.'id']
				));
				$test_2 = $this->table->q($where)->rowCount()==0;
				if($test_2){// a juz nie będzie? 
					$where = Utils::quote($where_tpl,array(
						$this->_current[$fix.'user_id'],$this->rq->getParam('v'),$this->_current[$fix.'id']
					));
					$test_3 = $this->table->q($where)->rowCount()!=0;
					return $test_3 ? 'Nie można przenosić aktualnego atrybutu ponizej wpisów historycznych.' :true;
				} //else return true;
			} //else return true;
		} //else return true;
		return true;
	}
	
	// editor
	public function historyIsDateOk($x){// unique for user
		if($this->rq->getParam('f')=='user_history_'.$x.'_date_since'){
			$test = $this->getTable()->q(
				Utils::quote(
					'user_history_'.$x.'_date_since = ? 
					AND user_history_'.$x.'_user_id = ? 
					AND user_history_'.$x.'_id != ?',
					array($this->rq->getParam('v'),
						$this->_current['user_history_'.$x.'_user_id'],
						$this->_current['user_history_'.$x.'_id'])
				)
			)->rowCount()>0;
			return $test ? 'Istnieje już taki wpis z identyczną datą dla tego użytkownika' : true;
		} else return true;
	}
	
	public function historyIsDateNotFuture($x,$isForm = false){
		
		$p = 'user_history_'.$x.'_date_since';
		
		if(!$isForm){
			if($this->rq->getParam('f')==$p){
				$test = strtotime($this->rq->getParam('v')) > time();
				return $test ? 'Data wpisu historii musi odnosić sie do przeszłości.':true;
			} else return true;	
		} else {
			$test = strtotime($this->rq->getParam($p)) > time();
			return $test ? 'Data wpisu historii musi odnosić sie do przeszłości.':true;
		}
	}
	
	// editor
	public function historyFinish($x,$addi = array(),$user_field = null,$condition_check = null){
		//Utils::log($this->_current);
		list($u,$h,$uid,$ds) = array('user_','user_history_'.$x,'_user_id','_date_since');
		
		if($this->rq->getParam('f')== $h.$ds){// edytujemy datę
			$test = $this->table->q(
				Utils::quote(
					$h.$ds.' > ? AND '.$h.$uid.' = ?',
					array($this->rq->getParam('v'),$this->_current[$h.$uid])
				)
			)->rowCount()==0;
			if($test){// edytujemy najnowszy
								
				$v = $this->_current[$h.'_'.$x.'_id'];
				
				if($condition_check && $condition_check($v) !== true){return;}
				
				$data = $user_field ? array($user_field=>$v) : array($u.$x.'_id'=>$v);
				
				$user = new User();			
				$user->updateById(array_merge($data,$addi),$this->_current[$h.$uid]);
			}
		} elseif($this->rq->getParam('f')== $h.'_'.$x.'_id'){// edytujemy typ
						
			$test = $this->table->q(
				Utils::quote(
					$h.$ds.' > ? AND '.$h.$uid.' = ?',
					array($this->_current[$h.$ds],$this->_current[$h.$uid])
				)
			)->rowCount()==0;
			if($test){// edytujemy najnowszy
				$user = new User();
				$v = $this->rq->getParam('v');
				
				if($condition_check && $condition_check($v) !== true){return;}
				
				$data = $user_field ? array($user_field=>$v) : array($u.$x.'_id'=>$v);
				
				$user->updateById(array_merge($data,$addi),$this->_current[$h.$uid]);
			}
		}
	}

	/**
	 * Funkcja zwraca wynik testu porównania danego parametru w requeście i przekazanym haszu.
	 * 
	 * Jeśli hasz jest pusty wynik bedzie pozytywny.
	 * To tylko uszczelnienie gdyż interfejs nie pozwala na takie modyfikacje.
	 * 
	 * @param Mixed (string|array) $param - array('param_current'=>'param_rq')
	 * @param Mixed (array|Boolean) $current
	 * @return Mixed (strting|Boolean true)
	 */
	static public function paramIsEqual($param,$current,$passNull = false){
		if(is_array($current)){
			$rq = Zend_Controller_Front::getInstance()->getRequest();
			$nulls = array(null,'');
			
			if(is_array($param)){
				foreach ($param as $k => $v){
					if(!Utils::isIn($rq->getParam($v,$nulls[0]),$nulls)){
						$test = $current[$k] != $rq->getParam($v);
					} else {
						return !$passNull ? "Parametr $v jest wymagany." : true;				
					}
				}
				$param = $v;// do komunikatu.
			} else {
				if(!Utils::isIn($rq->getParam($param,$nulls[0]),$nulls)){
					$test = $current[$param] != $rq->getParam($param);
				} else {
					return !$passNull? "Parametr $param jest wymagany." :  true;
				}
			}
			return $test ? "Krytyczny atrybut $param nie może być modyfikowany." : true;
		} else return true;
	}
	
	/**
	 * @param Validation $validator
	 * @param String $dateField
	 * @param String $objMsg
	 */
	static public function projectAttributOldness(Validation $validator,$dateField,$objMsg = 'edytować atrybutów projektu'){
		if(is_array($validator->_current)){
			if(!Utils::isIn(Utils::whoAmI(),array(Enum_Roles::Administrator,Enum_Roles::Developer))){ // wyjątek na admina.
				$test = Utils::getFirstDateOfMonth(date('Y'),date('m'))>
					strtotime($validator->_current[$dateField]." +$validator->_time_constraint months");
				return $test ? "Nie można $objMsg na więcej niż $validator->_time_constraint $validator->_month_word wstecz." : true;
			} else return true;
		}else return true;
	}
	
	/**
	 * @abstract Sprawdza czy zalogowany użytkownik ma prawo do czytania pod danym projektem. 
	 * @return mixed Boolean true or Validation Message.
	 * @param Mixed String or Int $project_id or empty (to use where json param)   
	 */
	static public function userCanSeeMe($project_id = null){// bierze project_id z where'a
		$role_id = Utils::whoAmI();
		if($role_id == Enum_Roles::Kadra) {// kadra
			if(!$project_id)$project_id = Utils::getValueFromWhere('project_id');
			$user_id = Utils::myId();
			$rq = Zend_Controller_Front::getInstance()->getRequest();
			$staff = new ProjectStaff();// TODO zmienic 6 na logiczny warunek (np że role nie lider , a stanowisko lider bedzie tylko dla lidera.)
			$test = $staff->fetchAll('user_id = '.$user_id.' AND project_role_id != 6 AND project_id = '.$project_id)->count()<1;
			return $test ? "Nie jestes przypisany do tego projektu" : true;
		} else if(!Utils::isIn($role_id,array(Enum_Roles::Developer,Enum_Roles::Administrator,Enum_Roles::TopManagement))){
			return 'Brak uprawnień do projektu.';
		} else return true;
	}
	
	/**
	 * @param $action_id or empty to get it from request param same name
	 */
	static public function userCanSeeMeByAction($action_id=null){
		$action_id = !$action_id ? Zend_Controller_Front::getInstance()->getRequest()->getParam('action_id'):$action_id;
		$action = new Action();
		$action_arr = $action->find($action_id)->current()->toArray();
		return self::userCanSeeMe($action_arr['project_id']);
	}
	
	/**
	 * OK jeśli user może widzieć chociaz jeden projekt klienta.
	 * @param $id_klient
	 */
	static public function userCanSeeMeByClient($id_klient){
		if (!Utils::isIn(Utils::whoAmI(),array(
			Enum_Roles::Administrator,Enum_Roles::Developer,Enum_Roles::TopManagement
		)) && !(
			Utils::whoAmI() == Enum_Roles::Kadra && Utils::myPosition()->project_role_all_clients == true
		)){
			if($id_klient && $id_klient != ''){
				$project = new Project();
				$projects = $project->fetchAll(Utils::quote('id_klient = ?',$id_klient));
				while($p = $projects->current()){
					$test = self::userCanSeeMe($p->project_id);
					if($test === true)return true;
					$projects->next();
				}
				return "Nie jesteś przypisany do żadnego projektu tego klienta.";
			} else return "Nie wskazano klienta.";
		} else return true;
	}
	
	/**
	 * DO ME!
	 * @param $project_id
	 */
	static public function userCanDoMe($project_id){
		$role_id = Utils::whoAmI();
		if($role_id == Enum_Roles::Kadra){
			if(!$project_id){
				$rq = Zend_Controller_Front::getInstance()->getRequest();
				if(!Utils::isIn($rq->getParam('where'),array(null,'')))
					$project_id = Utils::getValueFromWhere('project_id');
				// TODO sprawdzić czy nieZepsóło czegos getVBalueFromWhere [modyfikacja ostatni krok pobiera wprost z requestu)]
				// TODO przenieść do validacji projektu. - a tu zmienic opis na błąd. 
				else return "Kadra nie może tworzyć projektów.";
			}
			$user_id = Zend_Auth::getInstance()->getIdentity();
			$staff = new ProjectStaff();
			$test = $staff->q('user_id = '.$user_id.' AND p.project_role_can_edit = true AND project_id = '.$project_id,
				array(array('p'=>'project_role'),'p.project_role_id = #.project_role_id',array())
			)->rowCount()<1;
			return $test ? "Nie posiadasz wystarczających uprawnień." : true;
		} else if(!Utils::isIn($role_id,array(Enum_Roles::Developer,Enum_Roles::Administrator,Enum_Roles::TopManagement))){
			return 'Brak uprawnień do edycji projektu.';
		} else return true;
	}
	
	/**
	 * @param $action_id or empty to get it from request param same name
	 */
	static public function userCanDoMeByAction($action_id=null){
		$action_id = !$action_id ? Zend_Controller_Front::getInstance()->getRequest()->getParam('action_id'):$action_id;
		$action = new Action();
		$action_arr = $action->find($action_id)->current()->toArray();
		return self::userCanDoMe($action_arr['project_id']);
	}
	
	/**
	 * Czy w danym priojekcie user nie podlega w"wyjątkowi wynagrodzeń" - ZMIANA LOGIKI
	 * @deprecated TODO ? gdzie to jest uzyte
	 * @param Int $project_id
	 */
	static public function salaryException($project_id){
		$test = !(Utils::isIn(Utils::whoAmI(),array(Enum_Roles::Developer,Enum_Roles::Administrator,Enum_Roles::TopManagement)) 
			|| Utils::whatAmIDoingHere($project_id)==Enum_ProjectRoles::Kierownik);
		return $test ? "Nie posiadasz wystarczających uprawnień." : true;
	}
	
	static public function salaryExceptionPositions($project_id = null){
		if(!$project_id)$project_id = Utils::getValueFromWhere('project_id');
		
		$salary = new SalaryException();
		$q = $salary->fetchAll(Utils::quote('user_id = ? AND project_id = ?',array(Utils::myId(),$project_id)));
		
		if($q->count()>0){// wyjątek na projekt
			return $salary->getPositions($q->current()->salary_exception_id);
		} else { // wyjątek ogólny (uwzględniając MPK)
			$project = new Project();
			$mpk_id = $project->find($project_id)->current()->mpk_id;
			if($mpk_id && $mpk_id != ''){
				$q = $salary->q("project_id IS NULL AND mpk_id = $mpk_id AND user_id = ".Utils::myId(), 
					array(array('m'=>Utils::table('SalaryExceptionMpk')), '#.salary_exception_id = m.salary_exception_id', array('mpk_id')));		
				if($q->rowCount()>0){
					return $salary->getPositions($q->fetchObject()->salary_exception_id);
				} else {
					return false;
				}
			} else return false;
		}
	}
	
	static public function salaryExceptionByLocation($location_id){
		
		// TODO - czy masz na stanowisko danego loozera???
		// TODO !!
		
		$salary = new SalaryException();
				
		$test = $salary->q(// czy masz wyjątejk na projekt z tą lokalizacją
			Utils::quote('p.project_location = ? AND user_id = ?',array($location_id,Utils::myId())),
			array(array('p'=>'project'),'p.project_id = #.project_id',array(),'left')
		)->rowCount()==0;
		
		if($test){// czy masz na mpk
			$mpk = new Mpk();
			$q = $mpk->q('mpk_location_id = '.$location_id);
			$match = false;
			while ($the_mpk= $q->fetchObject()){
				if($the_mpk){
					$test2 = $salary->q(	// czy masz wyjatek na mpk?
						Utils::quote('m.mpk_id = ? AND user_id = ?',array($the_mpk->mpk_id,Utils::myId())),
						array(array('m'=>'salary_exception_mpk'),'m.salary_exception_id = #.salary_exception_id',array(),'left')
					)->rowCount()==0;
//					Validation::react(Utils::dump(array($the_mpk,$test2)));
					if(!$test2)$match = true;
				} 
			}
			return !$match ? 'Nie posiadasz uprawnień do przeglądania wynagrodzeń dla lokalizacji tego pracownka.' : true;
		} else return true;
	}
	
	static public function salaryExceptionByWorker($user_id){
		$user = new User();
		return self::salaryExceptionByLocation(
			$user->find($user_id)->current()->user_location_id
		);
	}
}

?>